package per.destiny.seckill.service;

import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import per.destiny.seckill.dto.OrderDto;
import per.destiny.seckill.exception.GlobalException;
import per.destiny.seckill.mapper.OrderMapper;
import per.destiny.seckill.mapper.SeckillGoodsMapper;
import per.destiny.seckill.mapper.SeckillOrderMapper;
import per.destiny.seckill.mapper.custom.CustomSeckillGoodsMapper;
import per.destiny.seckill.pojo.Order;
import per.destiny.seckill.pojo.SeckillGoods;
import per.destiny.seckill.pojo.SeckillGoodsExample;
import per.destiny.seckill.pojo.SeckillOrder;
import per.destiny.seckill.vo.GoodsVo;
import per.destiny.seckill.vo.OrderDetailVo;
import per.destiny.seckill.vo.RespBeanEnum;

import java.util.Date;
import java.util.List;

@Service
public class OrderService {

    @Autowired
    private SeckillGoodsMapper seckillGoodsMapper;

    @Autowired
    private CustomSeckillGoodsMapper customSeckillGoodsMapper;

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private SeckillOrderMapper seckillOrderMapper;

    @Autowired
    private GoodsService goodsService;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 接受的消息处理函数
     *
     * @param userId
     * @param goodsId
     */
    @Transactional
    public void seckillMessage(Long userId, Long goodsId) {
        GoodsVo goodsVo = goodsService.findGoodsVoById(goodsId);
        ValueOperations valueOperations = redisTemplate.opsForValue();
        // 再次判断库存以及是否重复下单
        if (goodsVo.getStockCount() < 1) {
            //判断数据库是否还有库存的标记，通过Redis设置，因为redis是分布式的，及时性比较好。
            //不能通过本地的内存标记“EMPTY_REDIS_STOCK_MAP”来访问，因为这个是为了减少Redis的访问来用的，Redis已经减至0了，此时肯定为true。
            valueOperations.set("isDBStockEmpty:" + goodsId, "");
            return;
        }
        // 判断是否重复抢购
        SeckillOrder seckillOrder = (SeckillOrder) valueOperations.get("order:" + userId + ":" + goodsId);
        if (seckillOrder != null) {
            return;
        }
        // 生成订单（推荐顺序，先生成订单，然后减库存。因为减库存虽然是行锁，但是其他所有请求都会要更改这条记录，在一个事务提交之前其他事务会排队，降低吞吐量）
        OrderDto orderDto = createOrder(userId, goodsVo);
        // 减库存 （推荐顺序，先生成订单，然后减库存。因为减库存虽然是行锁，但是其他所有请求都会要更改这条记录，在一个事务提交之前其他事务会排队，降低吞吐量）
        int count = decrStockCount(goodsVo.getId());
        if (count <= 0) {
            // 抛出异常，让事务回滚
            throw new GlobalException(RespBeanEnum.EMPTY_SOCKET);
        }
        // 将用户是否秒杀过的记录存储至Redis，减轻数据库压力，原本Controller层是查数据库判断的，现在改为了查Redis判断。
        valueOperations.set("order:" + userId + ":" + goodsVo.getId(), orderDto.getSeckillOrder());
    }

    private int decrStockCount(Long id) {
        return customSeckillGoodsMapper.decrStockCount(id);
    }

    private OrderDto createOrder(Long userId, GoodsVo goods) {
        OrderDto orderDto = new OrderDto();
        // 生成订单
        Order order = new Order();
        order.setUserId(userId);
        order.setGoodsId(goods.getId());
        order.setGoodsName(goods.getGoodsName());
        order.setGoodsCount(1);
        order.setGoodsPrice(goods.getSeckillPrice());
        order.setStatus(0);
        order.setCreateDate(new Date());
        order.setUpdateDate(new Date());
        orderMapper.insert(order);
        // 生成秒杀订单(本工程这里goodsId要跟userId生成唯一索引，防止高并发场景重复下单)
        SeckillOrder seckillOrder = new SeckillOrder();
        seckillOrder.setUserId(userId);
        seckillOrder.setOrderId(order.getId());
        seckillOrder.setGoodsId(goods.getId());
        seckillOrderMapper.insert(seckillOrder);
        orderDto.setOrder(order);
        orderDto.setSeckillOrder(seckillOrder);
        return orderDto;
    }

    private SeckillGoods getSeckillGoods(Long goodsId) {
        SeckillGoodsExample seckillGoodsExample = new SeckillGoodsExample();
        seckillGoodsExample.createCriteria().andGoodsIdEqualTo(goodsId);
        List<SeckillGoods> seckillGoods = seckillGoodsMapper.selectByExample(seckillGoodsExample);
        if (CollectionUtils.isEmpty(seckillGoods)) {
            return null;
        }
        return seckillGoods.get(0);
    }

    public OrderDetailVo detail(Long orderId) {
        if (orderId == null) {
            throw new GlobalException(RespBeanEnum.ORDER_NOT_EXIST);
        }
        OrderDetailVo orderDetailVo = new OrderDetailVo();
        Order order = orderMapper.selectByPrimaryKey(orderId);
        GoodsVo goodsVo = goodsService.findGoodsVoById(order.getGoodsId());
        orderDetailVo.setGoods(goodsVo);
        orderDetailVo.setOrder(order);
        return orderDetailVo;
    }

}
